from flask import Flask, request, Response, render_template
from flask_sqlalchemy import SQLAlchemy
from flask import jsonify
import cv2
import torch
import numpy as np
import random
from torchvision import transforms
from ultralytics import YOLO

from datetime import datetime,timedelta

from flask_socketio import SocketIO
from threading import Lock

from flask_cors import CORS

# 上传的视频
saveFilePath = ''
userQuestion = ''
SocketThread = None
thread_lock = Lock()
app = Flask(__name__)
CORS(app)
socketio = SocketIO()
socketio.init_app(app, cors_allowed_origins='*',async_mode='threading') # 用socketio初始化flask的app

HOSTNAME = '47.120.31.85'
PORT = 3306
USERNAME = 'root'
PASSWORD = '123456Ff'
DATABASE = 'schoolHelper'
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4'

db = SQLAlchemy(app)

###################讯飞星火###########################
import _thread as thread
import base64
import datetime
import hashlib
import hmac
import json
from urllib.parse import urlparse
import ssl
from datetime import datetime
from time import mktime
from urllib.parse import urlencode
from wsgiref.handlers import format_date_time

import websocket  # 使用websocket_client

answer = ""


class Ws_Param(object):
    # 初始化
    def __init__(self, APPID, APIKey, APISecret, Spark_url):
        self.APPID = APPID
        self.APIKey = APIKey
        self.APISecret = APISecret
        self.host = urlparse(Spark_url).netloc
        self.path = urlparse(Spark_url).path
        self.Spark_url = Spark_url

    # 生成url
    def create_url(self):
        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))

        # 拼接字符串
        signature_origin = "host: " + self.host + "\n"
        signature_origin += "date: " + date + "\n"
        signature_origin += "GET " + self.path + " HTTP/1.1"

        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
                                 digestmod=hashlib.sha256).digest()

        signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8')

        authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'

        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')

        # 将请求的鉴权参数组合为字典
        v = {
            "authorization": authorization,
            "date": date,
            "host": self.host
        }
        # 拼接鉴权参数，生成url
        url = self.Spark_url + '?' + urlencode(v)
        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释，比对相同参数时生成的url与自己代码生成的url是否一致
        return url


# 收到websocket错误的处理
def on_error(ws, error):
    print("### error:", error)


# 收到websocket关闭的处理
def on_close(ws, one, two):
    print(" ")


# 收到websocket连接建立的处理
def on_open(ws):
    thread.start_new_thread(run, (ws,))


def run(ws, *args):
    data = json.dumps(gen_params(appid=ws.appid, domain=ws.domain, question=ws.question))
    ws.send(data)


# 收到websocket消息的处理
def on_message(ws, message):
    data = json.loads(message)
    code = data['header']['code']
    if code != 0:
        print(f'请求错误: {code}, {data}')
        ws.close()
    else:
        choices = data["payload"]["choices"]
        status = choices["status"]
        content = choices["text"][0]["content"]
        print(content, end="")
        socketio.emit('server_response',{"data": content},namespace='/chat')
        global answer
        answer += content
        if status == 2:
            global SocketThread
            socketio.emit('server_response', {"data": "[over]"}, namespace='/chat')
            SocketThread = None
            ws.close()


def gen_params(appid, domain, question):
    """
    通过appid和用户的提问来生成请参数
    """
    data = {
        "header": {
            "app_id": appid,
            "uid": "1234"
        },
        "parameter": {
            "chat": {
                "domain": domain,
                "temperature": 0.5,
                "max_tokens": 2048
            }
        },
        "payload": {
            "message": {
                "text": question
            }
        }
    }
    return data


def startChat(appid, api_key, api_secret, Spark_url, domain, question):
    # print("星火:")
    wsParam = Ws_Param(appid, api_key, api_secret, Spark_url)
    websocket.enableTrace(False)
    wsUrl = wsParam.create_url()
    ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open)
    ws.appid = appid
    ws.question = question
    ws.domain = domain
    ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})


# length = 0


def getText(role, content):
    text = []
    jsoncon = {}
    jsoncon["role"] = role
    jsoncon["content"] = content
    text.append(jsoncon)
    return text


def getlength(text):
    length = 0
    for content in text:
        temp = content["content"]
        leng = len(temp)
        length += leng
    return length


def checklen(text):
    while (getlength(text) > 8000):
        del text[0]
    return text


# domain = "generalv3.5"      # Max版本
# domain = "generalv3"       # Pro版本
# domain = "general"         # Lite版本

# Spark_url = "wss://spark-api.xf-yun.com/v3.5/chat"   # Max服务地址
# Spark_url = "wss://spark-api.xf-yun.com/v3.1/chat"  # Pro服务地址
# Spark_url = "wss://spark-api.xf-yun.com/v1.1/chat"  # Lite服务地址

def config():
    config = {}
    config["appid"] = "3c591f62"
    config["api_secret"] = "Y2M2YWU0N2QzOGE0ODcyMmY3ODgzMWY2"
    config["api_key"] = "14a25fc24a288e7c358a0e795275aa57"
    config["domain"] = "generalv3"
    config["Spark_url"] = "wss://spark-api.xf-yun.com/v3.1/chat"
    return config
###################讯飞星火###########################

###################socketIO###########################
@socketio.on('connect', namespace='/chat')
def test_connect():
    global userQuestion
    userQuestion = request.args.get('question')
    print(userQuestion)
    """
    此函数在建立socket连接时被调用
    """
    print("socket 建立连接")
    global SocketThread
    with thread_lock:
        print(SocketThread)
        print(SocketThread is None)
        if SocketThread is None:
            print('开启新线程')
            # 如果socket连接，则开启一个线程，专门给前端发送消息
            SocketThread = socketio.start_background_task(target=background_thread)


@socketio.on('disconnect', namespace='/chat')
def disconnect_msg():
    """
    此函数在socket断开时被调用
    """
    print('client disconnected!')


def background_thread():
    try:
        cfg = config()
        appid = cfg["appid"]
        api_secret = cfg["api_secret"]
        api_key = cfg["api_key"]
        domain = cfg["domain"]
        Spark_url = cfg["Spark_url"]

        text = []
        text.clear()
        global userQuestion
        userQuestion = checklen(getText("user", userQuestion))
        startChat(appid, api_key, api_secret, Spark_url, domain, userQuestion)
    except Exception as e:
        print(e)

###################socketIO###########################

###################数据库##############################
def response(code,message,data):
    return jsonify({'message': message, 'code': code, 'data': data})

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(100), nullable=False)
    password = db.Column(db.String(100), nullable=False)

    def __init__(self, username, password):
        self.username = username
        self.password = password

class History(db.Model):
    __tablename__ = 'history'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    user_id = db.Column(db.Integer, nullable=False)
    kilometer = db.Column(db.Integer, nullable=False)
    create_date = db.Column(db.Date, nullable=False)

class HistoryMusic(db.Model):
    __tablename__ = 'history_music'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    user_id = db.Column(db.Integer, nullable=False)
    count = db.Column(db.Integer, nullable=False)
    create_date = db.Column(db.Date, nullable=False)

@app.route('/user/add',methods=['POST'])
def addUser():
    # 创建User对象
    username = request.get_json().get('username')
    password = request.get_json().get('password')
    user = User(username=username,password=password)
    # 将对象添加到db.session中
    db.session.add(user)
    # 将session的改变同步到数据库中
    db.session.commit()
    return response(200,'添加用户成功',None)

@app.route('/user/login',methods=['POST'])
def login():
    username = request.get_json().get('username')
    password = request.get_json().get('password')
    user = User.query.filter_by(username=username).first()
    if user and password == user.password:
        return response(200,'登陆成功',None)
    return response(500,'登陆失败',None)

@app.route('/user/updateMusicCount',methods=['POST'])
def updateMusicCount():
    username = request.get_json().get('username')
    user = User.query.filter_by(username=username).first()
    record = HistoryMusic.query.filter_by(user_id=user.id).first()
    if record is None:
        curren_datetime = datetime.now()
        record = HistoryMusic(user_id=user.id,count=0,create_date=curren_datetime.date())
        db.session.add(record)
        db.session.commit()
    else:
        record.count = record.count + 1
        db.session.commit()
    return response(200,'更新成功',None)

@app.route('/user/getMusicCount',methods=['GET'])
def getMusicCount():
    username = request.args.get('username')
    user = User.query.filter_by(username=username).first()
    record = HistoryMusic.query.filter_by(user_id=user.id).first()
    if record is None:
        curren_datetime = datetime.now()
        record = HistoryMusic(user_id=user.id,count=0, create_date=curren_datetime.date())
        db.session.add(record)
        db.session.commit()
    return response(200,'获取成功',record.count)

@app.route('/user/updateRunHistory',methods=['POST'])
def updateRunHistory():
    username = request.get_json().get('username')
    kilometer = request.get_json().get('kilometer')
    user = User.query.filter_by(username=username).first()
    record = History.query.filter_by(user_id=user.id).first()
    if record is None:
        curren_datetime = datetime.now()
        record = HistoryMusic(user_id=user.id,count=kilometer, create_date=curren_datetime.date())
        db.session.add(record)
        db.session.commit()
    else:
        record.kilometer = record.kilometer + kilometer
        db.session.commit()
    return response(200,'更新成功',None)

@app.route('/user/getRunDetail',methods=['GET'])
def getRunDetail():
    username = request.args.get('username')
    user = User.query.filter_by(username=username).first()
    records = History.query.filter_by(user_id=user.id).all()
    result = []
    runCount = 0
    runKilometer = 0
    if records is not None:
        for record in records:
            runCount = runCount + 1
            runKilometer = runKilometer + record.kilometer
    result.append(runCount)
    result.append(runKilometer)
    return response(200,'获取成功',result)

@app.route('/user/getWeeklyRunKilometer',methods=['GET'])
def getWeeklyRunKilometer():
    username = request.args.get('username')
    user = User.query.filter_by(username=username).first()
    # 获取当前日期
    today = datetime.today()
    # 计算一周前的日期
    one_week_ago = today - timedelta(days=7)
    # 生成最近一周的日期列表
    last_week_dates = [one_week_ago + timedelta(days=x) for x in range(7)]
    # 格式化日期为字符串
    last_week_dates_str = [date.strftime('%Y-%m-%d') for date in last_week_dates]
    weeklyDetail = []
    week = [date.strftime('%m-%d') for date in last_week_dates]
    for date in last_week_dates_str:
        history = History.query.filter_by(user_id=user.id).filter_by(create_date=date).first()
        if history is None:
            weeklyDetail.append(0)
        else:
            weeklyDetail.append(history.kilometer)
    result = []
    result.append(week)
    result.append(weeklyDetail)
    return response(200,'获取成功',result)

###################数据库##############################
@app.route('/openCVCamera',methods=['GET'])
def openCVCamera():
    ipCamera = 'http://admin:admin@10.204.26.151:8081/video'
    capture = cv2.VideoCapture(ipCamera)
    if capture is None or not capture.isOpened():
        print('Warning: unable to open video source: ', 0)
    else:
        model = YOLO(r'best.pt')
    return Response(generate_frames(capture, model, 0),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route("/getCameraHTML",methods=['GET'])
def getCameraHTML():
    return render_template('camera.html')

@app.route("/videoPredict",methods=['GET'])
def videoPre():
    model = YOLO(r'best.pt')
    return Response(generate_frames(None,model,1),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/uploadVideo',methods=['POST'])
def uploadVideo():
    # 获取上传文件数据
    file = request.files.get('file')
    # 保存文件到根目录
    global saveFilePath
    saveFilePath = './' + file.filename
    print(saveFilePath)
    file.save(file.filename)
    return response(200,'上传成功',file.filename)

@app.route("/getVideoHTML",methods=['GET'])
def getVideoHTML():
    return render_template('video.html')

def Load_image_cv2_input(img0, size):
    # Padded resize
    img = letterbox_lwk(img0, new_shape=size)[0]  # 填充调整大小
    '''
    lteerbox_image()将图片按照纵横比进行缩放，将空白部分用(128,128,128)填充,使图像尺寸满足输入要求
    具体操作就是,某个边正好可以等于目标长度,另一边小于等于目标长度
    将缩放后的数据拷贝到画布中心，即可完成输入变换
    '''
    # 转换
    img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
    img = np.ascontiguousarray(img)

    return img, img0
'''
letterbox的主要思想是尽可能的利用网络感受野的信息特征。比如在YOLOV5中最后一层的Stride=5，
即最后一层的特征图中每个点，可以对应原图中32X32的区域信息，那么只要在保证整体图片变换比例一致的情况下，
长宽均可以被32整除，那么就可以有效的利用感受野的信息。
'''
def letterbox_lwk(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
    # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
    # 在满足跨距多约束时调整大小和填充图像
    shape = img.shape[:2]  # current shape [height, width] 当前的高和宽
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleup:  # only scale down, do not scale up (for better test mAP)
        r = min(r, 1.0)
    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    if auto:  # minimum rectangle
        dw, dh = np.mod(dw, 32), np.mod(dh, 32)  # wh padding
    elif scaleFill:  # stretch
        dw, dh = 0.0, 0.0
        new_unpad = (new_shape[1], new_shape[0])
        ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return img, ratio, (dw, dh)

def plot_one_box(x, img, label=None, line_thickness=None):
    # Plots one bounding box on image img
    tl = line_thickness
    color = [random.randint(0, 255) for _ in range(4)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))

    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        tf = max(tl - 1, 1)  # font thickness
        t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
        c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
        cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
        cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)

def pic_pre(cv2_pic, model):
    imgsz = 640  # 模型标准尺寸 640
    img, _ = Load_image_cv2_input(cv2_pic, size=imgsz)

    img = torch.from_numpy(img)  # 将img转成张量
    toPIL = transforms.ToPILImage()  # 这个函数可以将张量转为PIL图片，由小数转为0-255之间的像素值
    img = toPIL(img)
    img.save('./pic.jpg')
    # 模型预测
    results = model.predict(source='./pic.jpg')
    '''
    --im0:resize之后的图片，该图片是用来进行网络预测用的。
    --im0s：没有resize之前的原始图片，用于最终的图片输出和目标框的绘制。
    '''
    img = cv2.imread('./pic.jpg')
    for result in results:
        for box in result.boxes:
            xmin = box.xyxy[0][0]
            ymin = box.xyxy[0][1]
            xmax = box.xyxy[0][2]
            ymax = box.xyxy[0][3]
            xyxy = (xmin, ymin, xmax, ymax)
            # 在原图中标注发现的目标：位置框+分类lable名称+置信度/可能性（--save-img）
            plot_one_box(xyxy, img, 'lane' ,line_thickness=3)
    return img

def process_frame(frame,model):
    # 在这里对视频帧进行处理
    img = pic_pre(frame, model)
    return img

def generate_frames(capture,model,mode):
    if mode == 1:
        capture = cv2.VideoCapture(saveFilePath)
    while True:
        ret, frame = capture.read()

        if not ret:
            break

        processed_frame = process_frame(frame,model)

        # 将处理后的帧转换为JPEG格式
        ret, buffer = cv2.imencode('.jpg', processed_frame)
        frame_data = buffer.tobytes()



        # 使用生成器生成视频帧
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame_data + b'\r\n')

if __name__ == '__main__':
    app.run()

